package com.atguigu.daijia.map.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.atguigu.daijia.common.constant.RedisConstant;
import com.atguigu.daijia.common.constant.SystemConstant;
import com.atguigu.daijia.common.util.LocationUtil;
import com.atguigu.daijia.driver.client.DriverInfoFeignClient;
import com.atguigu.daijia.map.repository.OrderServiceLocationRepository;
import com.atguigu.daijia.map.service.LocationService;
import com.atguigu.daijia.model.entity.driver.DriverSet;
import com.atguigu.daijia.model.entity.map.OrderServiceLocation;
import com.atguigu.daijia.model.form.map.OrderServiceLocationForm;
import com.atguigu.daijia.model.form.map.SearchNearByDriverForm;
import com.atguigu.daijia.model.form.map.UpdateDriverLocationForm;
import com.atguigu.daijia.model.form.map.UpdateOrderLocationForm;
import com.atguigu.daijia.model.vo.map.NearByDriverVo;
import com.atguigu.daijia.model.vo.map.OrderLocationVo;
import com.atguigu.daijia.model.vo.map.OrderServiceLastLocationVo;
import com.atguigu.daijia.order.client.OrderInfoFeignClient;
import lombok.extern.slf4j.Slf4j;
import org.bson.types.ObjectId;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class LocationServiceImpl implements LocationService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private DriverInfoFeignClient driverInfoFeignClient;

    @Autowired
    private OrderServiceLocationRepository orderServiceLocationRepository;

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private OrderInfoFeignClient orderInfoFeignClient;

    /**
     * 开启接单服务：更新司机经纬度位置
     *
     * @param updateDriverLocationForm
     * @return
     */
    @Override
    public Boolean updateDriverLocation(UpdateDriverLocationForm updateDriverLocationForm) {
        /**
         *  Redis GEO 主要用于存储地理位置信息，并对存储的信息进行相关操作，该功能在 Redis 3.2 版本新增。
         *  后续用在，乘客下单后寻找5公里范围内开启接单服务的司机，通过Redis GEO进行计算
         */
        Point point = new Point(updateDriverLocationForm.getLongitude().doubleValue(),
                updateDriverLocationForm.getLatitude().doubleValue());
        stringRedisTemplate.opsForGeo().add(RedisConstant.DRIVER_GEO_LOCATION,
                point,
                updateDriverLocationForm.getDriverId().toString());
        return true;
    }

    /**
     * 关闭接单服务：删除司机经纬度位置
     *
     * @param driverId
     * @return
     */
    @Override
    public Boolean removeDriverLocation(Long driverId) {
        stringRedisTemplate.opsForGeo().remove(RedisConstant.DRIVER_GEO_LOCATION, driverId.toString());
        return true;
    }

    /**
     * 搜索附近满足条件的司机
     *
     * @param searchNearByDriverForm
     * @return
     */
    @Override
    public List<NearByDriverVo> searchNearByDriver(SearchNearByDriverForm searchNearByDriverForm) {
        // 搜索经纬度位置5公里以内的司机
        //定义经纬度点
        Point point = new Point(searchNearByDriverForm.getLongitude().doubleValue(),
                searchNearByDriverForm.getLatitude().doubleValue());

        //定义距离：5公里(系统配置)
        Distance distance = new Distance(SystemConstant.NEARBY_DRIVER_RADIUS,
                RedisGeoCommands.DistanceUnit.KILOMETERS);
        //创建Circle对象  point  distance
        Circle circle = new Circle(point, distance);

        //定义GEO参数  设置返回结果包含内容
        RedisGeoCommands.GeoRadiusCommandArgs args =
                RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                        .includeDistance()    //包含距离
                        .includeCoordinates() //包含坐标
                        .sortAscending();     //升序

        // 1.GEORADIUS获取附近范围内的信息
        GeoResults<RedisGeoCommands.GeoLocation<String>> results =
                stringRedisTemplate.opsForGeo().radius(RedisConstant.DRIVER_GEO_LOCATION, circle, args);

        //2.收集信息，存入list
        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = results.getContent();

        //3.返回计算后的信息
        List<NearByDriverVo> list = new ArrayList();
        if (CollectionUtil.isNotEmpty(content)) {
            for (GeoResult<RedisGeoCommands.GeoLocation<String>> geoLocationGeoResult : content) {
                // 司机id
                Long driverId = Long.valueOf(geoLocationGeoResult.getContent().getName());
                //当前距离
                BigDecimal currentDistance = new BigDecimal(
                        geoLocationGeoResult.getDistance().getValue()).setScale(2, RoundingMode.HALF_UP);
                //获取司机接单设置参数
                DriverSet driverSet = driverInfoFeignClient.getDriverSet(driverId).getData();
                //接单里程判断，acceptDistance==0：不限制，
                if (driverSet.getAcceptDistance().doubleValue() != 0 &&
                        driverSet.getAcceptDistance().doubleValue() < currentDistance.doubleValue()) {
                    continue;
                }
                //订单里程判断，orderDistance==0：不限制
                if (driverSet.getOrderDistance().doubleValue() != 0
                        && driverSet.getOrderDistance().doubleValue()
                        < searchNearByDriverForm.getMileageDistance().doubleValue()) {
                    continue;
                }
                //满足条件的附近司机信息
                NearByDriverVo nearByDriverVo = new NearByDriverVo();
                nearByDriverVo.setDriverId(driverId);
                nearByDriverVo.setDistance(currentDistance);
                list.add(nearByDriverVo);
            }
        }
        return list;
    }

    /**
     * 司机赶往代驾起始点：更新订单地址到缓存
     *
     * @param updateOrderLocationForm
     * @return
     */
    @Override
    public Boolean updateOrderLocationToCache(UpdateOrderLocationForm updateOrderLocationForm) {
        OrderLocationVo orderLocationVo = new OrderLocationVo();
        BeanUtils.copyProperties(updateOrderLocationForm, orderLocationVo);
        //更新订单地址到缓存
        stringRedisTemplate.opsForValue().set(
                RedisConstant.UPDATE_ORDER_LOCATION + updateOrderLocationForm.getOrderId(),
                JSONUtil.toJsonStr(orderLocationVo));
        return true;
    }

    /**
     * 司机赶往代驾起始点：获取订单经纬度位置
     *
     * @param orderId
     * @return
     */
    @Override
    public OrderLocationVo getCacheOrderLocation(Long orderId) {
        String orderLocationVoStr = stringRedisTemplate.opsForValue()
                .get(RedisConstant.UPDATE_ORDER_LOCATION + orderId);
        return JSONUtil.toBean(orderLocationVoStr, OrderLocationVo.class);
    }

    /**
     * 开始代驾服务：保存代驾服务订单位置
     *
     * @param orderLocationServiceFormList
     * @return
     */
    @Override
    public Boolean saveOrderServiceLocation(List<OrderServiceLocationForm> orderLocationServiceFormList) {
        List<OrderServiceLocation> orderServiceLocationList =
                orderLocationServiceFormList.stream().map(orderServiceLocationForm -> {
                    OrderServiceLocation orderServiceLocation = new OrderServiceLocation();
                    BeanUtils.copyProperties(orderServiceLocationForm, orderServiceLocation);
                    orderServiceLocation.setId(ObjectId.get().toString());
                    orderServiceLocation.setCreateTime(new Date());
                    return orderServiceLocation;
                }).toList();
        //批量插入到MongoDB数据库中
        orderServiceLocationRepository.saveAll(orderServiceLocationList);
        return true;
    }

    /**
     * 获取订单最后一个位置信息
     *
     * @param orderId
     * @return
     */
    @Override
    public OrderServiceLastLocationVo getOrderServiceLastLocation(Long orderId) {
/*        OrderServiceLocation orderServiceLocation = new OrderServiceLocation();
        orderServiceLocation.setOrderId(orderId);
        Example<OrderServiceLocation> orderServiceLocationExample = Example.of(orderServiceLocation);
        List<OrderServiceLocation> orderServiceLocationList = orderServiceLocationRepository.findAll(
                orderServiceLocationExample,
                Sort.by(Sort.Direction.DESC, "createTime"));
        //封装返回对象
        OrderServiceLastLocationVo orderServiceLastLocationVo = new OrderServiceLastLocationVo();
        BeanUtils.copyProperties(orderServiceLocationList.get(0), orderServiceLastLocationVo);
        return orderServiceLastLocationVo;*/

        Query query = new Query(Criteria.where("orderId").is(orderId))
                .with(Sort.by(Sort.Order.desc("createTime")))
                .limit(1);
        OrderServiceLocation orderServiceLocation = mongoTemplate.findOne(query, OrderServiceLocation.class);
        OrderServiceLastLocationVo orderServiceLastLocationVo = new OrderServiceLastLocationVo();
        if (orderServiceLocation != null) {
            BeanUtils.copyProperties(orderServiceLocation, orderServiceLastLocationVo);
        }
        return orderServiceLastLocationVo;
    }

    /**
     * 代驾服务：计算订单实际里程
     *
     * @param orderId
     * @return
     */
    @Override
    public BigDecimal calculateOrderRealDistance(Long orderId) {
        // 1 根据订单id获取代驾订单的位置信息（升序）
        // 查询MongoDB
        //第一种方式：
//        Query query = new Query(Criteria.where("orderId").is(orderId))
//                .with(Sort.by(Sort.Direction.ASC, "createTime"));
//        List<OrderServiceLocation> orderServiceLocationList =
//                mongoTemplate.find(query, OrderServiceLocation.class);
        //第二种方式  根据命名规范自定义方法查询
        List<OrderServiceLocation> orderServiceLocationList =
                orderServiceLocationRepository.getByOrderIdOrderByCreateTimeAsc(orderId);

        //2第一步查询返回订单位置信息的list集合
        //把list集合遍历，得到每个位置的信息，计算两个位置距离
        double realDistance = 0;
        if (CollectionUtil.isNotEmpty(orderServiceLocationList)) {
            for (int i = 0; i < orderServiceLocationList.size() - 1; i++) {
                BigDecimal longitude1 = orderServiceLocationList.get(i).getLongitude();
                BigDecimal latitude1 = orderServiceLocationList.get(i).getLatitude();
                BigDecimal longitude2 = orderServiceLocationList.get(i + 1).getLongitude();
                BigDecimal latitude2 = orderServiceLocationList.get(i + 1).getLatitude();

                double distance = LocationUtil.getDistance(
                        latitude1.doubleValue(), longitude1.doubleValue(),
                        latitude2.doubleValue(), longitude2.doubleValue());
                //把计算的距离相加
                realDistance += distance;
            }
        }
        //3最后返回总距离
        //测试过程中，没有真正代驾，实际代驾GPS位置没有变化，模拟：实际代驾里程 = 预期里程 + 5
        if (realDistance == 0) {
            return orderInfoFeignClient.getOrderInfo(orderId).getData()
                    .getExpectDistance().add(new BigDecimal("5"));
        }
        return new BigDecimal(realDistance);
    }
}
